#include "coin2/exchange/base/symbology/product_holder_manager.h"

#include <iostream>
#include <string>

#include <coin/proto/symbology.pb.h>
#include <gtest/gtest.h>
#include <nlohmann/json.hpp>

#include "coin2/exchange/factory/symbology/product.h"

TEST(ProductHolderManager, GetProductHolder_Extract) {
  coin::proto::ProductHolderManagerConfig config;
  config.set_fallback_day(0);
  config.set_root_dir("cc/testcoin2/exchange/base/symbology/coin_product_info");
  coin2::exchange::base::symbology::ProductHolderManager product_holder_manager(config);

  auto exchange = coin::proto::Binance;
  int64_t timestamp{1642464000'000'000'000LL}; // 2022-01-18 00:00:00

  {
    auto market = coin::proto::Spot;
    std::string symbol{"BTC-USDT"};

    auto product = CreateProductFromNormString(market, exchange, symbol, timestamp);
    auto* product_holder = product_holder_manager.GetProductHolder(*product, timestamp);
    EXPECT_EQ(product_holder->product(), *product);

    const auto& product_info = product_holder->product_info();
    EXPECT_EQ(product_info.is_inverse(), false);
    EXPECT_NEAR(product_info.min_amount(), 10, 1e-8);
    EXPECT_NEAR(product_info.min_qty(), 1e-5, 1e-8);
  }

  {
    auto market = coin::proto::Futures;
    std::string symbol{"BTC-USDT.QUARTER"};

    auto product = CreateProductFromNormString(market, exchange, symbol, timestamp);
    auto* product_holder = product_holder_manager.GetProductHolder(*product, timestamp);
    EXPECT_EQ(product_holder->product(), *product);

    const auto& product_info = product_holder->product_info();
    EXPECT_EQ(product_info.is_inverse(), false);
    EXPECT_NEAR(product_info.min_amount(), 5, 1e-5);
  }

  {
    auto market = coin::proto::Futures;
    std::string symbol{"BTC-USD.QUARTER"};

    auto product = CreateProductFromNormString(market, exchange, symbol, timestamp);
    auto* product_holder = product_holder_manager.GetProductHolder(*product, timestamp);
    EXPECT_EQ(product_holder->product(), *product);

    const auto& product_info = product_holder->product_info();
    EXPECT_EQ(product_info.is_inverse(), true);
    EXPECT_NEAR(product_info.contract_value(), 100, 1e-5);
  }
}

// TEST(ProductHolderManager, GetProductHolder_Latest) {
//   coin::proto::ProductHolderManagerConfig config;
//   config.set_search_policy(coin::proto::ProductHolderManagerConfig::Latest);
//   config.set_root_dir("cc/testcoin2/exchange/base/symbology/coin_product_info");
//   coin2::exchange::base::symbology::ProductHolderManager product_holder_manager(config);

//   auto exchange = coin::proto::Binance;
//   int64_t timestamp{1642464000'000'000'000LL};

//   {
//     auto market = coin::proto::Spot;
//     std::string symbol{"BTC-USDT"};

//     auto product = CreateProductFromNormString(market, exchange, symbol, timestamp);
//     // Timestamp is ignored in this case
//     auto* product_holder = product_holder_manager.GetProductHolder(*product, 0);
//     EXPECT_EQ(product_holder->product(), *product);

//     const auto& product_info = product_holder->product_info();
//     EXPECT_EQ(product_info.is_inverse(), false);
//     EXPECT_NEAR(product_info.min_amount(), 10, 1e-8);
//     EXPECT_NEAR(product_info.min_qty(), 1e-5, 1e-8);
//   }

//   {
//     auto market = coin::proto::Futures;
//     std::string symbol{"BTC-USDT.QUARTER"};

//     auto product = CreateProductFromNormString(market, exchange, symbol, timestamp);
//     auto* product_holder = product_holder_manager.GetProductHolder(*product, 0);
//     EXPECT_EQ(product_holder->product(), *product);

//     const auto& product_info = product_holder->product_info();
//     EXPECT_EQ(product_info.is_inverse(), false);
//     EXPECT_NEAR(product_info.min_amount(), 5, 1e-5);
//   }

//   {
//     auto market = coin::proto::Futures;
//     std::string symbol{"BTC-USD.QUARTER"};

//     auto product = CreateProductFromNormString(market, exchange, symbol, timestamp);
//     auto* product_holder = product_holder_manager.GetProductHolder(*product, 0);
//     EXPECT_EQ(product_holder->product(), *product);

//     const auto& product_info = product_holder->product_info();
//     EXPECT_EQ(product_info.is_inverse(), true);
//     EXPECT_NEAR(product_info.contract_value(), 100, 1e-5);
//   }
// }

TEST(ProductHolderManager, GetProductHolder_FallbackToPrevious) {
  coin::proto::ProductHolderManagerConfig config;
  config.set_fallback_day(7); //default
  config.set_root_dir("cc/testcoin2/exchange/base/symbology/coin_product_info");
  coin2::exchange::base::symbology::ProductHolderManager product_holder_manager(config);

  auto exchange = coin::proto::Binance;
  int64_t timestamp{1642809600'000'000'000LL};

  {
    auto market = coin::proto::Spot;
    std::string symbol{"BTC-USDT"};

    auto product = CreateProductFromNormString(market, exchange, symbol, timestamp);
    // Timestamp is ignored in this case
    auto* product_holder = product_holder_manager.GetProductHolder(*product, timestamp);
    EXPECT_EQ(product_holder->product(), *product);

    const auto& product_info = product_holder->product_info();
    EXPECT_EQ(product_info.is_inverse(), false);
    EXPECT_NEAR(product_info.min_amount(), 10, 1e-8);
    EXPECT_NEAR(product_info.min_qty(), 1e-5, 1e-8);
  }

  {
    auto market = coin::proto::Futures;
    std::string symbol{"BTC-USDT.QUARTER"};

    auto product = CreateProductFromNormString(market, exchange, symbol, timestamp);
    auto* product_holder = product_holder_manager.GetProductHolder(*product, timestamp);
    EXPECT_EQ(product_holder->product(), *product);

    const auto& product_info = product_holder->product_info();
    EXPECT_EQ(product_info.is_inverse(), false);
    EXPECT_NEAR(product_info.min_amount(), 5, 1e-5);
  }

  {
    auto market = coin::proto::Futures;
    std::string symbol{"BTC-USD.QUARTER"};

    auto product = CreateProductFromNormString(market, exchange, symbol, timestamp);
    auto* product_holder = product_holder_manager.GetProductHolder(*product, timestamp);
    EXPECT_EQ(product_holder->product(), *product);

    const auto& product_info = product_holder->product_info();
    EXPECT_EQ(product_info.is_inverse(), true);
    EXPECT_NEAR(product_info.contract_value(), 100, 1e-5);
  }
}

// TEST(ProductHolderManager, GetProductHolder_Oldway) {
//   coin::proto::ProductHolderManagerConfig config;
//   config.set_fallback_day(7); //default
//   config.set_root_dir("cc/testcoin2/exchange/base/symbology/coin_product_info_wrong_path");
//   coin2::exchange::base::symbology::ProductHolderManager product_holder_manager(config);

//   auto exchange = coin::proto::Binance;
//   int64_t timestamp{1642809600'000'000'000LL};

//   {
//     auto market = coin::proto::Spot;
//     std::string symbol{"BTC-USDT"};

//     auto product = CreateProductFromNormString(market, exchange, symbol, timestamp);
//     // Timestamp is ignored in this case
//     auto* product_holder = product_holder_manager.GetProductHolder(*product, timestamp);
//     EXPECT_EQ(product_holder->product(), *product);

//     const auto& product_info = product_holder->product_info();
//     EXPECT_EQ(product_info.is_inverse(), false);
//     EXPECT_NEAR(product_info.min_amount(), 10, 1e-8);
//     EXPECT_NEAR(product_info.min_qty(), 1e-5, 1e-8);
//   }

//   {
//     auto market = coin::proto::Futures;
//     std::string symbol{"BTC-USDT.QUARTER"};

//     auto product = CreateProductFromNormString(market, exchange, symbol, timestamp);
//     auto* product_holder = product_holder_manager.GetProductHolder(*product, timestamp);
//     EXPECT_EQ(product_holder->product(), *product);

//     const auto& product_info = product_holder->product_info();
//     EXPECT_EQ(product_info.is_inverse(), false);
//     EXPECT_NEAR(product_info.min_amount(), 5, 1e-5);
//   }

//   {
//     auto market = coin::proto::Futures;
//     std::string symbol{"BTC-USD.QUARTER"};

//     auto product = CreateProductFromNormString(market, exchange, symbol, timestamp);
//     auto* product_holder = product_holder_manager.GetProductHolder(*product, timestamp);
//     EXPECT_EQ(product_holder->product(), *product);

//     const auto& product_info = product_holder->product_info();
//     EXPECT_EQ(product_info.is_inverse(), true);
//     EXPECT_NEAR(product_info.contract_value(), 100, 1e-5);
//   }
// }
